{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "raw_mimetype": "text/restructuredtext"
   },
   "source": [
    ".. _nb_portfolio_allocation:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Portfolio Allocation\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this quick tutorial, the portfolio allocation problem shall be investigated. Of course, this is not financial advice in any way but should illustrate how multi-objective optimization can be applied to a quite interesting problem."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let us start by loading some data for illustration purposes. Feel free to use your own."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "from pymoo.util.remote import Remote\n",
    "\n",
    "file = Remote.get_instance().load(\"examples\", \"portfolio_allocation.csv\", to=None)\n",
    "df = pd.read_csv(file, parse_dates=True, index_col=\"date\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This tutorial is based on the Markowitz Mean-Variance Portfolio Theory and thus, we need to calculate the mean returns and covariances:"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {
    "raw_mimetype": "text/restructuredtext"
   },
   "source": [
    ".. admonition:: Info\n",
    "    :class: myOwnStyle\n",
    "\n",
    "    Note that the problem in this case study can be solved directly using a quadratic solver (which will be much more efficient). However, such a solver finds only a single solution and must run multiple times to approximate the Pareto-optimal front. Moreover, it is worth noting that if we slightly change the problem to cubic or non-polynomial, it can not be applied anymore. The method shown provides more flexibility, for instance, optimizing objectives derived from Monte-Carlo sampling."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "returns = df.pct_change().dropna(how=\"all\")\n",
    "mu = (1 + returns).prod() ** (252 / returns.count()) - 1\n",
    "cov = returns.cov() * 252\n",
    "\n",
    "mu, cov = mu.to_numpy(), cov.to_numpy()\n",
    "\n",
    "labels = df.columns\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "fig, ax = plt.subplots(figsize=(10, 5))\n",
    "k = np.arange(len(mu))\n",
    "ax.bar(k, mu)\n",
    "ax.set_xticks(k, labels, rotation = 90)\n",
    "plt.show()\n",
    "\n",
    "\n",
    "f = plt.figure(figsize=(10, 10))\n",
    "plt.matshow(returns.corr(), fignum=f.number)\n",
    "plt.xticks(k, labels, fontsize=12, rotation=90)\n",
    "plt.yticks(k, labels, fontsize=12)\n",
    "cb = plt.colorbar()\n",
    "cb.ax.tick_params(labelsize=14)\n",
    "plt.title('Correlation Matrix', fontsize=16)\n",
    "print(\"DONE\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then let us define an optimization problem based on the theory mentioned above:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pymoo.core.problem import ElementwiseProblem\n",
    "\n",
    "class PortfolioProblem(ElementwiseProblem):\n",
    "\n",
    "    def __init__(self, mu, cov, risk_free_rate=0.02, **kwargs):\n",
    "        super().__init__(n_var=len(df.columns), n_obj=2, xl=0.0, xu=1.0, **kwargs)\n",
    "        self.mu = mu\n",
    "        self.cov = cov\n",
    "        self.risk_free_rate = risk_free_rate\n",
    "\n",
    "    def _evaluate(self, x, out, *args, **kwargs):\n",
    "        exp_return = x @ self.mu\n",
    "        exp_risk = np.sqrt(x.T @ self.cov @ x)\n",
    "        sharpe = (exp_return - self.risk_free_rate) / exp_risk\n",
    "\n",
    "        out[\"F\"] = [exp_risk, -exp_return]\n",
    "        out[\"sharpe\"] = sharpe"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we should consider one more fact. The variable `x` defines what percentage we will invest in what product. Thus, it can not be more than 100\\% in total. Moreover, an investment of a very small fraction does not really make sense. Thus we also incorporate each weight to be at least `1e-3` of the overall investment.\n",
    "\n",
    "To ensure both, we can use a `Repair` operator (also see [here](../constraints/repair.ipynb)) which will directly be used by the optimization method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pymoo.core.repair import Repair\n",
    "\n",
    "class PortfolioRepair(Repair):\n",
    "\n",
    "    def _do(self, problem, X, **kwargs):\n",
    "        X[X < 1e-3] = 0\n",
    "        return X / X.sum(axis=1, keepdims=True)\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let us see what solutions are found to be optimal:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pymoo.algorithms.moo.sms import SMSEMOA\n",
    "from pymoo.optimize import minimize\n",
    "\n",
    "problem = PortfolioProblem(mu, cov)\n",
    "\n",
    "algorithm = SMSEMOA(repair=PortfolioRepair())\n",
    "\n",
    "res = minimize(problem,\n",
    "               algorithm,\n",
    "               seed=1,\n",
    "               verbose=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The algorithm has obtained a Pareto-optimal set trading off the mean return and volatility of the portfolio. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X, F, sharpe = res.opt.get(\"X\", \"F\", \"sharpe\")\n",
    "F = F * [1, -1]\n",
    "max_sharpe = sharpe.argmax()\n",
    "\n",
    "plt.scatter(F[:, 0], F[:, 1], facecolor=\"none\", edgecolors=\"blue\", alpha=0.5, label=\"Pareto-Optimal Portfolio\")\n",
    "plt.scatter(cov.diagonal() ** 0.5, mu, facecolor=\"none\", edgecolors=\"black\", s=30, label=\"Asset\")\n",
    "plt.scatter(F[max_sharpe, 0], F[max_sharpe, 1], marker=\"x\", s=100, color=\"red\", label=\"Max Sharpe Portfolio\")\n",
    "plt.legend()\n",
    "plt.xlabel(\"expected volatility\")\n",
    "plt.ylabel(\"expected return\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A common way for the decision making is looking at the sharpe ratio shown below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import operator\n",
    "\n",
    "allocation = {name: w for name, w in zip(df.columns, X[max_sharpe])}\n",
    "allocation = sorted(allocation.items(), key=operator.itemgetter(1), reverse=True)\n",
    "\n",
    "print(\"Allocation With Best Sharpe\")\n",
    "for name, w in allocation:\n",
    "    print(f\"{name:<5} {w}\")"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
